﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net;
using System.Reflection;
using System.Threading;
using System.Web;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using iLib.Web.UI;
using Ionic.Zip;

namespace Cms.Utilities
{
    public class Updater : IUpdater
    {
        #region IUpdater Members

        public void ApplyDatabasePatches()
        {
            Logging.LogInfo("Enter ApplyDatabasePatches");
            while (!IsApplicationRunning())
                Thread.Sleep(5000);
            var currentVersion = new Version(ConfigurationManager.AppSettings["Version"]);
            Logging.LogInfo("Version before database patching: " + currentVersion.Full);
            DatabaseUpdater databaseUpdater = DatabaseUpdater.Create();
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.7.10175.105");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.7.10175.107");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.7.10180.115");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.8.10182.123");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.8.10228.130");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.10229.157");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.10232.182");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.10232.183");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.10237.184");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.10237.191");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.10243.211");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.11064.247");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.11095.262");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.11095.264");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.11095.265");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.11133.300");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.11137.306");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.11139.313");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.11163.317");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.11170.327");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.11227.337");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.11229.341");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12016.369");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12021.372");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12026.376");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12052.387");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12073.394");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12078.398");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12114.419");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12141.437");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12165.446");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12216.454");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12256.457");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12297.462");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12303.463");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12309.464");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12309.468");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12310.469");
            ApplyDatabasePatch(databaseUpdater, currentVersion, "0.9.12325.471");
            Logging.LogInfo("Exit ApplyDatabasePatches");
        }

        public Version GetCurrentVersion()
        {
            // This didn't work under medium trust:
            //Assembly a = Assembly.GetExecutingAssembly();
            //System.Diagnostics.FileVersionInfo fvi = System.Diagnostics.FileVersionInfo.GetVersionInfo(a.Location);
            //return new Version(fvi.FileVersion);

            // Use this instead:
            return new Version(ConfigurationManager.AppSettings["Version"]);
        }

        public Version GetLatestVersion()
        {
            return UpdateServiceWrapper.GetLatestVersion();
        }
        
        public void RemoveHttpModules()
        {
            Logging.LogInfo("Enter RemoveHttpModules");
            XDocument config = XDocument.Load(Url.PhysicalPath + "web.config");
            config.XPathSelectElement("/configuration/system.web/httpModules/add[@name='SessionManager']").Remove();
            config.XPathSelectElement("/configuration/system.web/httpModules/add[@name='RequestStart']").Remove();
            config.XPathSelectElement("/configuration/system.web/httpModules/add[@name='UrlRewriter']").Remove();
            config.XPathSelectElement("/configuration/system.webServer/modules/add[@name='SessionManager']").Remove();
            config.XPathSelectElement("/configuration/system.webServer/modules/add[@name='RequestStart']").Remove();
            config.XPathSelectElement("/configuration/system.webServer/modules/add[@name='UrlRewriter']").Remove();
            var writer = new XmlTextWriter(Url.PhysicalPath + "web.config", null) { Formatting = Formatting.Indented };
            config.WriteTo(writer);
            writer.Flush();
            writer.Close();
            Logging.LogInfo("Exit RemoveHttpModules");
        }
        
        public void Unzip()
        {
            Logging.LogInfo("Enter Unzip");

            // Overwrite files
            byte[] package = UpdateServiceWrapper.GetUpdatePackage();
            if (package == null)
                return;
            using (ZipFile zip = ZipFile.Read(package))
            {
                foreach (ZipEntry entry in zip)
                {
                    entry.Extract(Url.PhysicalPath, ExtractExistingFileAction.OverwriteSilently);
                }
            }
            Logging.LogInfo("Exit Unzip");
        }

        public void ApplyConfigPatches()
        {
            Logging.LogInfo("Enter ApplyConfigPatches");
            var currentVersion = new Version(ConfigurationManager.AppSettings["Version"]);
            Logging.LogInfo("Version before config patching: " + currentVersion.Full);
            try
            {
                XDocument patches = XDocument.Load("http://update.fredium.com/FrediumLatestUpdate/configpatches.xml");
                IDictionary<string, XDocument> configFiles = new Dictionary<string, XDocument>();
                foreach (XElement patch in patches.XPathSelectElements("/patches/patch"))
                {
                    if (currentVersion.IsGreaterThan(new Version(patch.Attribute("version").Value)))
                        continue;
                    Logging.LogInfo("Apply patch " + patch.Attribute("version").Value);
                    foreach (XElement file in patch.Elements())
                    {
                        string path = Url.PhysicalPath + file.Attribute("path").Value.Replace("/", "\\");
                        if (!configFiles.ContainsKey(path))
                            configFiles.Add(path, XDocument.Load(path));
                        XDocument configFile = configFiles[path];
                        foreach (XElement action in file.Elements())
                        {
                            XElement element;
                            switch (action.Name.ToString())
                            {
                                case "addElement":
                                    XElement parentNode = configFile.XPathSelectElement(action.Attribute("xpath").Value);
                                    element = new XElement(action.Attribute("name").Value);
                                    if (action.Attribute("value").Value != string.Empty)
                                    {
                                        element.SetValue(action.Attribute("value").Value);
                                    }
                                    parentNode.Add(element);
                                    if (action.HasElements)
                                    {
                                        foreach (XElement child in action.Elements())
                                        {
                                            element.Add(new XAttribute(child.Attribute("name").Value, child.Attribute("value").Value));
                                        }
                                    }
                                    break;
                                case "removeElement":
                                    configFile.XPathSelectElement(action.Attribute("xpath").Value).Remove();
                                    break;
                                case "addAttribute":
                                    Logging.LogInfo("AddAttribute: ");
                                    element = configFile.XPathSelectElement(action.Attribute("xpath").Value);
                                    element.Add(new XAttribute(action.Attribute("name").Value, action.Attribute("value").Value));
                                    break;
                                case "removeAttribute":
                                    element = configFile.XPathSelectElement(action.Attribute("xpath").Value);
                                    element.Attribute(action.Attribute("name").Value).Remove();
                                    break;
                                case "modifyAttribute":
                                    element = configFile.XPathSelectElement(action.Attribute("xpath").Value);
                                    element.Attribute(action.Attribute("name").Value).SetValue(action.Attribute("value").Value);
                                    break;
                            }
                        }
                    }
                }
                foreach (var kvp in configFiles)
                {
                    var writer = new XmlTextWriter(kvp.Key, null) { Formatting = Formatting.Indented };
                    kvp.Value.WriteTo(writer);
                    writer.Flush();
                    writer.Close();
                }
            }
            catch (Exception ex)
            {
                Logging.LogError("ApplyConfigPatches error: ", ex);
            }
            Logging.LogInfo("Exit ApplyConfigPatches");
        }

        public void RestoreHttpModules()
        {
            Logging.LogInfo("Enter RestoreHttpModules");
            XDocument config = XDocument.Load(Url.PhysicalPath + "web.config");
            XElement element = config.XPathSelectElement("/configuration/appSettings/add[@key='Version']");
            element.Attribute("value").SetValue(GetLatestVersion().Full);
            element = config.XPathSelectElement("/configuration/system.web/httpModules");
            element.Add(new XElement("add", new XAttribute("name", "SessionManager"), new XAttribute("type", "Cms.Data.ORM.SessionManager, Cms.Data")));
            element.Add(new XElement("add", new XAttribute("name", "RequestStart"), new XAttribute("type", "Cms.Web.Framework.Infrastructure.RequestStart, Cms.Web.Framework")));
            element.Add(new XElement("add", new XAttribute("name", "UrlRewriter"), new XAttribute("type", "Cms.Web.Framework.Infrastructure.UrlRewriter, Cms.Web.Framework")));
            element = config.XPathSelectElement("/configuration/system.webServer/modules");
            element.Add(new XElement("add", new XAttribute("name", "SessionManager"), new XAttribute("type", "Cms.Data.ORM.SessionManager, Cms.Data")));
            element.Add(new XElement("add", new XAttribute("name", "RequestStart"), new XAttribute("type", "Cms.Web.Framework.Infrastructure.RequestStart, Cms.Web.Framework")));
            element.Add(new XElement("add", new XAttribute("name", "UrlRewriter"), new XAttribute("type", "Cms.Web.Framework.Infrastructure.UrlRewriter, Cms.Web.Framework")));
            var writer = new XmlTextWriter(Url.PhysicalPath + "web.config", null) { Formatting = Formatting.Indented };
            config.WriteTo(writer);
            writer.Flush();
            writer.Close();
            Logging.LogInfo("Exit RestoreHttpModules");
        }

        #endregion

        private void ApplyDatabasePatch(DatabaseUpdater databaseUpdater, Version currentVersion, string newVersion)
        {
            if (!currentVersion.IsLessThanOrEqualTo(newVersion))
                return;
            MethodInfo patchMethodInfo = databaseUpdater.GetType().GetMethod("Patch_" + newVersion.Replace(".", "_"), BindingFlags.Public | BindingFlags.Instance);
            Delegate patchMethodDelegate = Delegate.CreateDelegate(typeof(DatabaseUpdater.PatchDelegate), databaseUpdater, patchMethodInfo);
            databaseUpdater.ApplyPatch(newVersion, (DatabaseUpdater.PatchDelegate)patchMethodDelegate);
        }
        
        private bool IsApplicationRunning()
        {
            if (HttpContext.Current == null)
            {
                return true;
            }
            HttpRequest request = HttpContext.Current.Request;
            int port = request.Url.Port;
            string url = request.IsSecureConnection ? "https://" : "http://";
            url += request.Url.Host;
            if (port != 80 && port != 443)
            {
                url += ":" + port;
            }
            url += request.ApplicationPath;
            if (!url.EndsWith("/"))
            {
                url += "/";
            }
            url += "Admin/Default.aspx";
            var req = (HttpWebRequest)WebRequest.Create(url);
            req.Timeout = 60000;
            HttpWebResponse resp;
            bool result = false;
            try
            {
                resp = (HttpWebResponse)req.GetResponse();
                if (resp.StatusCode == HttpStatusCode.OK)
                    result = true;
            }
            catch (WebException ex)
            {
                if (ex.Status == WebExceptionStatus.ProtocolError)
                {
                    resp = ex.Response as HttpWebResponse;
                    if (resp != null)
                    {
                    }
                }
            }
            return result;
        }
    }
}